package cn.bossfriday.im.api.http;

import cn.bossfriday.common.exception.ServiceRuntimeException;
import cn.bossfriday.common.http.HttpProcessorMapper;
import cn.bossfriday.im.common.api.ApiResponseHelper;
import cn.bossfriday.im.common.entity.result.ResultCode;
import cn.bossfriday.im.common.enums.api.ApiRequestType;
import cn.bossfriday.im.common.helper.AppHelper;
import cn.bossfriday.im.common.helper.ImServerApiHelper;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.FullHttpRequest;
import lombok.extern.slf4j.Slf4j;

import java.net.URI;
import java.util.Objects;

import static cn.bossfriday.im.common.constant.ApiConstant.*;

/**
 * HttpApiServerHandler
 *
 * @author chenx
 */
@Slf4j
public class HttpApiServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        FullHttpRequest httpRequest = null;
        try {
            if (msg instanceof FullHttpRequest) {
                httpRequest = (FullHttpRequest) msg;
                this.onMessageReceived(ctx, httpRequest);
            }
        } finally {
            if (httpRequest != null && httpRequest.refCnt() > 0) {
                httpRequest.release();
            }
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        log.error("HttpApiServerHandler.exceptionCaught()", cause);
        if (ctx.channel().isActive()) {
            ctx.channel().close();
        }
    }

    /**
     * onMessageReceived
     */
    private void onMessageReceived(ChannelHandlerContext ctx, FullHttpRequest httpRequest) {
        try {
            URI uri = new URI(httpRequest.uri());
            ApiRequestType requestType = ApiRequestType.find(httpRequest.method().name(), uri);
            if (Objects.isNull(requestType)) {
                ApiResponseHelper.sendApiResponse(ctx, ResultCode.API_UNSUPPORTED);
                return;
            }

            if (requestType.isApiAuth()) {
                ResultCode authResult = ImServerApiHelper.auth(httpRequest);
                if (authResult.getCode() != ResultCode.OK.getCode()) {
                    ApiResponseHelper.sendApiResponse(ctx, authResult);
                    return;
                }
            }

            if (!this.isAppValid(ctx, httpRequest)) {
                ApiResponseHelper.sendApiResponse(ctx, ResultCode.API_INVALID_APP_KEY);
                return;
            }

            String apiVersion = requestType.getUrlParser().parsePath(uri).get(HTTP_URL_ARGS_API_VERSION);
            ctx.channel().attr(ATTRIBUTE_KEY_API_VERSION).set(apiVersion);

            HttpProcessorMapper.getHttpProcessor(requestType.getApiRouteKey()).process(ctx, httpRequest);
        } catch (Exception ex) {
            if (ex instanceof ServiceRuntimeException) {
                log.warn("HttpApiServerHandler.onMessageReceived() failed!", ex);
                ApiResponseHelper.sendApiResponse(ctx, ResultCode.SYSTEM_ERROR.getCode(), ex.getMessage());
                return;
            }

            log.error("HttpApiServerHandler.onMessageReceived() error!", ex);
            ApiResponseHelper.sendApiResponse(ctx, ResultCode.SYSTEM_ERROR);
        }
    }

    /**
     * isAppValid
     */
    private boolean isAppValid(ChannelHandlerContext ctx, FullHttpRequest httpRequest) {
        try {
            long appId = AppHelper.getAppId(httpRequest.headers().get(HTTP_HEADER_APP_KEY));
            AppHelper.getAppInfo(appId);
            ctx.channel().attr(ATTRIBUTE_KEY_APP_ID).set(appId);

            return true;
        } catch (Exception ex) {
            return false;
        }
    }
}
